package com.ling.web.video.extractor.impl

import android.util.Base64
import android.util.Log
import com.ling.web.video.extractor.model.WebModel
import com.ling.web.video.extractor.WebVideoExtractor
import com.ling.web.video.extractor.callback.OnParseCallback
import com.ling.web.video.extractor.model.PriorityMode
import com.ling.web.video.extractor.utils.WebExtractorUtil
import com.ling.web.video.extractor.utils.ThreadPoolUtil
import okhttp3.OkHttpClient
import okhttp3.Request
import java.net.URLDecoder

/**
 * @author Ling
 * @date :2024/11/11 16:29
 */
class WebVideoExtractorImpl private constructor() : WebVideoExtractor {

    private var okHttpClient : OkHttpClient? = null
    private var currentRunnable : Runnable? = null
    private var currentParseWebUrl = ""
    private val timeOut = 20L
    //已经解析过的资源地址，不再重新解析
    private var parsedResourceList = arrayListOf<String>()

    companion object{
        private var singleInstance : WebVideoExtractor? = null

        @JvmStatic
        @Synchronized
        fun getSingleInstance() : WebVideoExtractor {
            if(singleInstance == null){
                singleInstance = WebVideoExtractorImpl()
            }
            return singleInstance!!
        }
    }

    override fun clearParsedCache() {
        currentParseWebUrl = ""
        parsedResourceList.clear()
        try {
            if(currentRunnable != null){
                ThreadPoolUtil.generateExecutor().remove(currentRunnable)
            }
        } catch (e: Exception) {
            Log.e("--->", "clearParsedCache error ${e.message}")
        }
    }

    override fun extractorVideoByPriorityMode(title: String, webUrl: String, resourceUrl: String, priorityMode: PriorityMode , onParseCallback: OnParseCallback?) {
        //如果侧重于api解析，则尽量把视频链接交予API进行解析
        if(priorityMode == PriorityMode.API){
            if(WebExtractorUtil.getApiEnableByApiMode(webUrl)){
                extractorVideoWithApi(webUrl , onParseCallback)
            }else{
                extractorVideoWithResource(title , webUrl , resourceUrl , onParseCallback)
            }
        }else{
            if(WebExtractorUtil.getApiEnableByResourceMode(webUrl)){
                extractorVideoWithApi(webUrl , onParseCallback)
            }else{
                extractorVideoWithResource(title , webUrl , resourceUrl , onParseCallback)
            }
        }
    }

    override fun extractorYoutubeVideo(webUrl: String, onParseCallback: OnParseCallback?) {
        extractorVideoWithApi(webUrl, onParseCallback)
    }

    override fun extractorVideoWithApi(webUrl: String , onParseCallback: OnParseCallback?) {
        if(webUrl.isEmpty()) return
        //如果是同一个视频,禁止重复请求
        if(!canParsedApiVideo(webUrl)) return
        Log.e("--->api", "start $webUrl")
        //移除当前正在执行的任务
        if(currentRunnable != null){
            try {
                ThreadPoolUtil.generateExecutor().remove(currentRunnable)
            } catch (e: Exception) {
                Log.e("--->api", "error ${e.message}")
            }
        }
        currentRunnable = Runnable {
            try {
                var url = webUrl
                //youtube内嵌视频执行转换逻辑
                if(WebExtractorUtil.isYoutubeWebsite(webUrl)){
                    url = WebExtractorUtil.transferYoutubeUrlToDefault(webUrl)
                }
                //开始请求
                val result = getOkHttpClient()
                    .newCall(WebExtractorUtil.getApiRequest(url))
                    .execute()

                if(result.isSuccessful){
                    val resultStr = result.body?.string()
                    if(resultStr.isNullOrEmpty()) return@Runnable
                    Log.e("--->api", "result $resultStr")
                    val data = WebExtractorUtil.parseJsonForApiResult(webUrl , resultStr)
                    if(data.isNullOrEmpty()) return@Runnable
                    if(currentParseWebUrl.isEmpty()) return@Runnable
                    if(!ThreadPoolUtil.isOnMainThread()){
                        ThreadPoolUtil.runOnMainThread {
                            onParseCallback?.onResult(data)
                        }
                    }else{
                        onParseCallback?.onResult(data)
                    }
                }else{
                    Log.e("--->api", "fail ${result.message}")
                }
            } catch (e: Exception) {
                Log.e("--->api", "error ${e.message}")
            }
        }
        //开始执行
        if(ThreadPoolUtil.isOnMainThread()){
            ThreadPoolUtil.generateExecutor().execute(currentRunnable)
        }else{
            currentRunnable?.run()
        }
    }

    override fun extractorVideoWithResource(title: String , webUrl: String , resourceUrl: String, onParseCallback: OnParseCallback?) {
        try {
            //过滤一些无用资源文件
            if(webUrl.isEmpty() || resourceUrl.isEmpty()) return
            if(WebExtractorUtil.isFlitterResource(resourceUrl)) return
                //部分网站内嵌youtube视频
            if(WebExtractorUtil.isYoutubeEmbed(resourceUrl)){
                if(canParsedResourceVideo(webUrl , resourceUrl)){
                    extractorVideoWithApi(resourceUrl , onParseCallback)
                }
                return
            }
            Log.e("--->resource", "start $resourceUrl")
            //facebook、imdb
            if(WebExtractorUtil.isFaceBookWebsite(webUrl) || WebExtractorUtil.isImdbWebsite(webUrl)){
                extractorFaceBookVideo(title , webUrl , resourceUrl, false, onParseCallback)
                return
            }
            //vimeo
            if(WebExtractorUtil.isVimeoWebsite(webUrl)){
                extractorVimeoVideo(title , webUrl , resourceUrl, false, onParseCallback)
                return
            }
            //buzz
            if(WebExtractorUtil.isBuzzVideoWebsite(webUrl)){
                extractorBuzzVideo(title , webUrl , resourceUrl, false,  onParseCallback)
                return
            }
            //twitch
            if(WebExtractorUtil.isTwitchWebsite(webUrl)){
                extractorTwitchVideo(title , webUrl , resourceUrl, false,  onParseCallback)
                return
            }
            //instagram
            if(WebExtractorUtil.isInstagramWebsite(webUrl)){
                extractorInstagramVideo(title , webUrl , resourceUrl, false,  onParseCallback)
                return
            }
            //yahoo
            if(WebExtractorUtil.isYahooWebsite(webUrl)){
                extractorYahooVideo(title , webUrl , resourceUrl, onParseCallback)
                return
            }
            //espn
            if(WebExtractorUtil.isEspnWebsite(webUrl)){
                extractorEspnVideo(title , webUrl , resourceUrl, onParseCallback)
                return
            }
            //需要排除以上已经解析过的
            if(containsParsed(webUrl)) return
            //其他网站带有mp4、m3u8关键字的
            if (resourceUrl.contains(".mp4" , true) || resourceUrl.contains("m3u8" , true) || resourceUrl.contains("m3u" , true)) {
                if (canParsedResourceVideo(webUrl , resourceUrl)) {
                    defaultResourceVideoResult(title , webUrl , resourceUrl, onParseCallback)
                }
                return
            }
            //其他资源使用head请求，判断content-type是否包含video关键字
            if (canParsedResourceVideo(webUrl , resourceUrl)) {
                extractorHeadVideo(title , webUrl , resourceUrl , onParseCallback)
            }
        }catch (e : Exception){
            Log.e("---->", "extractorVideoWithResource error ${e.message}")
        }
    }

    override fun extractorFaceBookVideo(title: String , webUrl: String , resourceUrl: String , useApi : Boolean , onParseCallback: OnParseCallback?){
        if(useApi){
            extractorVideoWithApi(webUrl , onParseCallback)
        }else{
            if(!canParsedResourceVideo(webUrl , resourceUrl)) return
            if (resourceUrl.contains(".mp4" , true) || resourceUrl.contains("m3u8" , true) || resourceUrl.contains("m3u" , true)) {
                defaultResourceVideoResult(title, webUrl, resourceUrl, onParseCallback)
            }
        }
    }

    override fun extractorImdbVideo(title: String , webUrl: String , resourceUrl: String , useApi : Boolean , onParseCallback: OnParseCallback?){
        extractorFaceBookVideo(title , webUrl , resourceUrl , useApi , onParseCallback)
    }

    override fun extractorVimeoVideo(title: String, webUrl: String, resourceUrl: String, useApi: Boolean , onParseCallback: OnParseCallback?) {
        if(useApi){
            extractorVideoWithApi(webUrl , onParseCallback)
        }else{
            if(!canParsedResourceVideo(webUrl , resourceUrl)) return
            try {
                if((resourceUrl.contains(".m3u8" , true) || resourceUrl.contains(".mp4" , true)) && (resourceUrl.contains("&range" , true) || resourceUrl.contains("?pathsig=" , true))) {
                    val rangeIndex = resourceUrl.lastIndexOf("&range=")
                    if(rangeIndex > 0){
                        var vimeoUrl = resourceUrl.substring(0 , rangeIndex)
                        if(parsedResourceList.contains(vimeoUrl)) return
                        parsedResourceList.add(vimeoUrl)
                        vimeoUrl = URLDecoder.decode(WebExtractorUtil.transferUrlToRead(vimeoUrl) ,"utf-8")
                        val rIndex = vimeoUrl.lastIndexOf("r=")
                        if(rIndex > 0){
                            var rStr = vimeoUrl.substring(rIndex + 2)
                            rStr = Base64.decode(rStr , Base64.DEFAULT).decodeToString()
                            if(!rStr.contains("east1" , true)) return
                            defaultResourceVideoResult(title, webUrl, vimeoUrl, onParseCallback)
                        }
                    }
                }
            } catch (e: Exception) {
                Log.e("--->", "extractorVimeoVideo error ${e.message}")
            }
        }
    }

    override fun extractorBuzzVideo(title: String, webUrl: String, resourceUrl: String, useApi: Boolean ,  onParseCallback: OnParseCallback?) {
        if(useApi){
            extractorVideoWithApi(webUrl, onParseCallback)
        }else{
            if(!canParsedResourceVideo(webUrl , resourceUrl)) return
            if(resourceUrl.contains(".m3u8" , true) && (resourceUrl.contains("mp4_h264_aac" , true) || resourceUrl.startsWith("https://vod3.cf.dmcdn.net" , true))){
                defaultResourceVideoResult(title, webUrl, resourceUrl, onParseCallback)
            }
        }
    }

    override fun extractorYahooVideo(title: String, webUrl: String, resourceUrl: String, onParseCallback: OnParseCallback?) {
        if(!canParsedResourceVideo(webUrl , resourceUrl)) return
        if(resourceUrl.startsWith("https://manifest.prod.boltdns.net" , true) && resourceUrl.contains("master.m3u8" , true)){
            defaultResourceVideoResult(title, webUrl, resourceUrl, onParseCallback)
        }
    }

    override fun extractorTwitchVideo(title: String, webUrl: String, resourceUrl: String, useApi: Boolean ,  onParseCallback: OnParseCallback?) {
        if(useApi){
            extractorVideoWithApi(webUrl, onParseCallback)
        }else{
            if(!canParsedResourceVideo(webUrl , resourceUrl)) return
            try {
                if (resourceUrl.contains(".mp4" , true) || resourceUrl.contains("m3u8" , true) || resourceUrl.contains("m3u" , true)) {
                    val nameIndex = webUrl.lastIndexOf("/")
                    if(nameIndex > 0){
                        val nameStr = webUrl.substring(nameIndex + 1)
                        if(resourceUrl.contains(nameStr , true) && resourceUrl.startsWith("https://usher.ttvnw.net/" , true)){
                            defaultResourceVideoResult(title, webUrl, resourceUrl, onParseCallback)
                        }
                    }
                }
            }catch (e : Exception){
                Log.e("--->", "extractorTwitchVideo error ${e.message}")
            }
        }
    }

    override fun extractorInstagramVideo(title: String, webUrl: String, resourceUrl: String, useApi: Boolean , onParseCallback: OnParseCallback?) {
        if(useApi){
            extractorVideoWithApi(webUrl, onParseCallback)
        }else{
            if(!canParsedResourceVideo(webUrl , resourceUrl)) return
            try {
                if (resourceUrl.contains(".mp4" , true) || resourceUrl.contains("m3u8" , true) || resourceUrl.contains("m3u" , true)) {
                    defaultResourceVideoResult(title , webUrl , resourceUrl , onParseCallback)
                }
            }catch (e : Exception){
                Log.e("--->", "extractorInstagramVideo error ${e.message}")
            }
        }
    }

    override fun extractorEspnVideo(title: String, webUrl: String, resourceUrl: String, onParseCallback: OnParseCallback?) {
        if(!canParsedResourceVideo(webUrl , resourceUrl)) return
        if (resourceUrl.contains(".mp4" , true) || resourceUrl.contains("m3u8" , true) || resourceUrl.contains("m3u" , true)) {
            if(resourceUrl.startsWith("https://service-pkgespn.akamaized.net" , true) || resourceUrl.endsWith("playlist.m3u8" , true)){
                defaultResourceVideoResult(title, webUrl, resourceUrl, onParseCallback)
            }
        }
    }

    override fun extractorFoxVideo(title: String, webUrl: String, resourceUrl: String , onParseCallback: OnParseCallback?) {
        if(!canParsedResourceVideo(webUrl , resourceUrl)) return
        extractorVideoWithApi(webUrl , onParseCallback)
    }

    override fun extractorHeadVideo(title: String , webUrl: String, resourceUrl: String, onParseCallback: OnParseCallback?) {
        if(!canParsedResourceVideo(webUrl , resourceUrl)) return
        try {
            val runnable = Runnable{
                try {
                    val headRequest = Request.Builder().url(resourceUrl)
                        .head()
                        .build()

                    val headResponse = getOkHttpClient().newCall(headRequest).execute()
                    if(headResponse.isSuccessful){
                        val contentType = headResponse.header("content-type" , "")
                        if (!contentType.isNullOrEmpty() && contentType.contains("video")) {
                            Log.e("--->head", "result: $resourceUrl")
                            defaultResourceVideoResult(title, webUrl, resourceUrl, onParseCallback)
                        }
                    }else{
                        Log.e("--->head", "fail ${headResponse.message}")
                    }
                } catch (e: Exception) {
                    Log.e("--->head", "error ${e.message}")
                }
            }

            if(ThreadPoolUtil.isOnMainThread()){
                ThreadPoolUtil.generateExecutor().execute(runnable)
            }else{
                runnable.run()
            }
        } catch (e: Exception) {
            Log.e("--->head", "error ${e.message}")
            return
        }
    }

    private fun defaultResourceVideoResult(title: String, webUrl: String, resourceUrl: String, onParseCallback: OnParseCallback?){
        if(currentParseWebUrl.isEmpty()) return@Runnable
        Log.e("--->result", resourceUrl)
        val webModel = WebModel(
            resourceName = title ,
            mimeType = "video/mp4" ,
            parseFromUrl = webUrl ,
            resourceUrl = resourceUrl
        )
        if(!ThreadPoolUtil.isOnMainThread()){
            ThreadPoolUtil.runOnMainThread {
                onParseCallback?.onResult(listOf(webModel))
            }
        }
        else{
            onParseCallback?.onResult(listOf(webModel))
        }
    }

    private fun canParsedResourceVideo(webUrl: String , resourceUrl: String) : Boolean{
        //如果是同一个站点,把相同的链接不再次解析
        if(currentParseWebUrl == webUrl){
            if(parsedResourceList.contains(resourceUrl)){
                return false
            }else{
                parsedResourceList.add(resourceUrl)
            }
        }else{
            //如果不同的站点，则把缓存的解析链接清除
            currentParseWebUrl = webUrl
            parsedResourceList.clear()
        }
        return true
    }

    private fun canParsedApiVideo(webUrl: String) : Boolean{
        if(currentParseWebUrl == webUrl) return false
        currentParseWebUrl = webUrl
        return true
    }

    private fun getOkHttpClient(): OkHttpClient {
        if(okHttpClient == null){
            okHttpClient = WebExtractorUtil.getOkHttpClint(timeOut)
        }
        return okHttpClient!!
    }

    private fun containsParsed(webUrl: String) : Boolean{
        return webUrl.contains("youtube.com" , true) ||
                webUrl.contains("facebook.com" , true) ||
                webUrl.contains("vimeo.com" , true) ||
                webUrl.contains("imdb.com" , true) ||
                webUrl.contains("instagram.com" , true) ||
                webUrl.contains("buzzvideos.com" , true) ||
                webUrl.contains("twitch.com" , true) ||
                webUrl.contains("yahoo.com" , true) ||
                webUrl.contains("foxnews.com" , true) ||
                webUrl.contains("espn.com" , true)
    }
}